package ru.ifmo.rybakov.muclumon.analysis;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.joda.time.Seconds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.rybakov.math.RegressionAnalyzer;
import ru.ifmo.rybakov.muclumon.DateTimeValue;
import ru.ifmo.rybakov.muclumon.NumericValue;
import ru.ifmo.rybakov.muclumon.Properties;
import ru.ifmo.rybakov.muclumon.Property;
import ru.ifmo.rybakov.muclumon.PropertyManager;
import ru.ifmo.rybakov.muclumon.PropertyType;
import ru.ifmo.rybakov.muclumon.PropertyValue;
import ru.ifmo.rybakov.muclumon.Task;

public class TaskClusterExponentialRegressionAnalyzer {
	
	Map<Property, Integer> positions = new HashMap<Property, Integer>();
	Map<Integer, Property> properties = new HashMap<Integer, Property>();
	Map<Property, Map<String, Double> > values = new HashMap<Property, Map<String, Double>>();
	
	private static final Logger logger = LoggerFactory.getLogger(TaskClusterExponentialRegressionAnalyzer.class);
	
	private int inputSize;
	private TaskCluster cluster;
	private RegressionAnalyzer analyzer;
	
	public TaskClusterExponentialRegressionAnalyzer(TaskCluster cluster) {
		this.cluster = cluster;
		logger.debug("Building dependencies");
		Collection<Relation> relations = buildRelations();
		double[][] input = new double[relations.size()][inputSize];
		double[] output = new double[relations.size()];
		
		int i = 0;
		for (Relation relation : relations) {
			double[] line = relation.getInput();
			for (int k = 0; k < line.length; k++) {
				input[i][k] = line[k];
			}
			output[i] = relation.getOutput();
			i++;
		}
		
		try {
			logger.debug("Constructing internal regression analyzer");
			analyzer = new RegressionAnalyzer(input, output);
			logger.debug("Running internal regression analyzer");
			analyzer.run();
			logger.debug("Analyzed successfully, regression coefficients are:");
			double[] result = analyzer.getResult();
			System.out.print("*\t");
//			for (int j = 0; j < (result.length - 1); j++) {
//				System.out.print(properties.get(j).getName() + "\t");
//			}
			System.out.println();
			for (int j = 0; j < result.length; j++) {
				System.out.print(result[j] + "\t");
			}
			System.out.println();
			System.out.print("*\t");
			for (int j = 0; j < (result.length - 1); j++) {
				Double m = 0.0;
				Double v = null;
				Integer c = 0;
				for (int k = 0; k < relations.size(); k++) {
					m += input[k][j];
				}
				m /= relations.size();
				for (int k = 0; k < relations.size(); k++) {
					if (input[k][j] != 0) { // степень влияния 
						if (v == null) v = 0.0;
						v += result[j + 1] * (m - input[k][j]) / output[k];
						c++;
					}
				}
				System.out.print(v == null ? "0\t" : (Math.abs(v / c) + "\t"));
			}
			System.out.println();
			logger.debug("Ready to predict!");
		} catch (Exception e) {
			logger.error("Must not be", e);
		}
	}
	
	public static Set<Property> getRegressionProperties(TaskCluster cluster) {
		Set<Property> properties = new HashSet<Property>();
		for (Task task : cluster.getTasks()) {
			properties.addAll(getRegressionProperties(task));
		}
		return properties;
	}
	
	public static Set<Property> getRegressionProperties(Task task) {
		Set<Property> properties = new HashSet<Property>();
		for (Property p : task.getPropertySet()) {
			if (PropertyType.REGRESSION.equals(p.getType())) {
				properties.add(p);
			}
		}
		return properties;
	}
	
	/**
	 * This method builds and returns the collection of relations between regression properties
	 * of tasks and corresponding execution time. As input property values it uses only
	 * {@link PropertyType#REGRESSION} property values. It uses {@link Properties#TASK_STARTED}
	 * and {@link Properties#TASK_FINISHED} values to calculate time of task execution.
	 * @return Collection of relations
	 */
	private Collection<Relation> buildRelations() {
		List<Relation> samples = new ArrayList<Relation>(); // usage of set prevents from clones to be appeared in result

		// build collection of properties to be analyzed
		Set<Property> properties = getRegressionProperties(cluster);
		
		Integer i = 0;
		for (Property p : properties) { // order properties in table
			System.out.print(p.getName() + "\t");
			positions.put(p, i);
			this.properties.put(i, p);
			i++;
		}
		System.out.println();
		inputSize = properties.size();
		
		for (Task task : cluster.getTasks()) {
			DateTimeValue startDateTime = (DateTimeValue) task.getSingleValue(PropertyManager.get(Properties.TASK_STARTED));
			DateTimeValue finishDateTime = (DateTimeValue) task.getSingleValue(PropertyManager.get(Properties.TASK_FINISHED));
			double seconds = Seconds.secondsBetween(startDateTime.getValue(), finishDateTime.getValue()).getSeconds();		
			Relation sample = buildTimeRelation(task, inputSize, seconds);
			if (!samples.contains(sample)) {
				samples.add(sample);
			}
		}
		
		for (Relation sample : samples) {
			for (int j = 0; j < sample.getInput().length; j++) {
				System.out.print(sample.getInput()[j] + "\t");
			}
			System.out.print("[" + sample.getOutput() + "]");
			System.out.println();
		}
		
		return samples;
	}
	
	// this method is used to calculate numeric equivalents for string properties
	private Relation buildTimeRelation(Task task, int sampleSize, double time) {
		double[] input = new double[sampleSize];
		Arrays.fill(input, 0);
		for (PropertyValue pv : task.getPropertyValues()) {
			Integer pos = positions.get(pv.getProperty());
			if (pos == null) {
				continue;
			}
			if (pv instanceof NumericValue) {
				double v = ((NumericValue) pv).getValue();
				input[pos] = v;//Math.pow(Math.E, v);
				// input[pos+1] = v;
			}
		}
		return new Relation(input, Math.log(time));
	}
}
